/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package searcherPackage.Ranking;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import searcherPackage.Index.Index;
import searcherPackage.Index.PostingsList;
import searcherPackage.Index.Term;

/**
 *
 * @author i_khri
 */
public class BM25Ranker{

    public static BM25Ranker newInstance(Index termsMapping, PostingsList docIds, int collectionSize, double avgDocLen, Map<Integer, Integer> docsLengths){
        return new BM25Ranker(termsMapping, docIds, collectionSize, avgDocLen, docsLengths);
    }



    /**
     * 
     * @return ordered set of document ids.
     */
    public List<Integer> rank(){
        List<Integer> ranked =  new ArrayList<Integer>();
        Set<Term> terms = termsMapping.keySet();
        List<RankingListEntry> RSVs = new ArrayList<RankingListEntry>();

        // calculate RSV for all documents


        for(PostingsList.Entry entry : docIds){
            // lookup this document in the terms mappings and calculate partial RSV for each match.
            int docId = entry.getDocId();
            int docLength = docsLenghts.get(docId);
            double partialRSV = 0.0;


            for(Term term : terms){
                PostingsList postingsList = termsMapping.getPostingsList(term);
                int df = postingsList.size();

                // locate document with id docId in the term's postings list.
                PostingsList.Entry documentInPostingsList = postingsList.getEntryByDocId(docId);
                if(documentInPostingsList == null){
                    continue;
                }
                
                // if matching entry found, calculate rank for the document.
                double log = Math.log10((double)collectionSize / df); // as long as termsMapping does not have entries for terms with empty postings lists, df should never be 0.
                double tf = documentInPostingsList.getTermFrequency();
               
                double numerator = (dfScale + 1) * tf;

                double denominator = dfScale * ((1 - docLengthScale) + docLengthScale * (docLength / avgDocLen)) + tf;
                partialRSV += (double)log * ((double) numerator / denominator);
            }
            RSVs.add(new RankingListEntry(partialRSV, docId)); // here partialRSV is already complete.

            // DEBUG
            System.out.println(docId + " : " + partialRSV);
            // -----

        }

        Collections.sort(RSVs);
        for(RankingListEntry listEntry : RSVs){
            ranked.add(listEntry.docId);
        }

        return ranked;
    }



    public double getDfScale(){
        return dfScale;
    }



    public double getDocLengthScale(){
        return docLengthScale;
    }



    public void setDfScale(double dfScale){
        this.dfScale = dfScale;
    }


    public void setDocLengthScale(double docLengthScale){
        this.docLengthScale = docLengthScale;
    }


    private BM25Ranker(Index termsMapping, PostingsList docIds, int collectionSize, double avgDocLen, Map<Integer, Integer> docsLenghts){
        this.docIds = docIds;
        this.termsMapping = termsMapping;
        this.collectionSize = collectionSize;
        this.avgDocLen = avgDocLen;
        this.docsLenghts = docsLenghts;
    }



    private static class RankingListEntry implements Comparable<RankingListEntry>{
        // for sorting in reverse order.
        public int compareTo(RankingListEntry o) {
            return rank.compareTo(o.rank) * (-1);
        }
        private RankingListEntry(Double r, Integer d){
            rank = r;
            docId = d;
        }
        private Double rank;
        private Integer docId;
    }



    private Index termsMapping;
    private PostingsList docIds;
    private int collectionSize; // in documents
    private double avgDocLen;   // avarage document length
    private Map<Integer, Integer> docsLenghts; // map to get length of each document from.

    // tuning parameters:
    private double dfScale = 0.0;          // k1. infinitely large.
    private double docLengthScale = 0.0;   // b. 0 to 1.
}
